#import "TiffManager.h"
#import "Thinker.h"

								//I tested on 32Mb memory workstation
								//it caused flicker if bigger than this
#define MAXIMAGESIZE 150		//scale down if bigger than this
						
#define SIZEINC  2				//increase image by number of pixels
								// for each frame in animation
								// this will affect how many images will
								//be built for each animation
								//the smaller the increment the smoother it is
								
//#define DEBUG
 
@implementation TiffManager

- init
{
char buf[MAXPATHLEN + 1];

	buf[0] = (int)NULL;
    [super init];
		
	[self setHomeDirFile:buf];	//if there is a .Celestial folder in
									//home directory - use it instead
									
    [self setTiffDirectory :buf];

    [self buildTiffStorage];
		
	if (totalTiffCount < 1){ //no files in .Celestial folder
								 // get them from module folder
								 
		moduleDir = [[NXApp delegate] moduleDirectory:"StarShip"];
    	strcpy(buf, moduleDir);
    	strcat(buf,"/Celestial.bundle");
    	[self setTiffDirectory :buf];
		[self buildTiffStorage];

	}
	else
		printf("Using files in .Celestial folder\n");						 

	tiffStorageIndex = 0;
	animationIndex = 0;
	numberProcessed = 0;
	animFileIndex = 1;
	maxAnimFiles = 0;
    return self;
    
}


//looks in the tiffStorage and starts creating images one at a time
//and returns so that something else can be going on while this is
//happening - returns Done when they are created.

- (BOOL)createTiffs
{

char buf[MAXPATHLEN + 1];
char *imageName = 0;
char *animDirName = 0;
ImageStruct imageStruct;
int isSpin = 0;
int isAnim = 0;
int isMult = 0;
#ifdef DEBUG
NXRect DebugRect; 
#endif


NXPoint bogusPoint = {0.0,0.0};			//composite image to create cache
NXRect	bogusRect = {0.0,0.0,1.0,1.0};  //composite image to create cache
float increasePercent;
NXPoint offset;
NXPoint oldCenter;
NXPoint newCenter;
NXPoint newTrans;
float distance,origAngle,maxDiagonal;
		

// tiffStorageIndex = 0 to totalTiffCount-1

	isSpin = ((ImageStruct *)[tiffStorage elementAt:
		tiffStorageIndex])->isSpin;
	isMult = ((ImageStruct *)[tiffStorage elementAt:
		tiffStorageIndex])->isMult;
	isAnim = ((ImageStruct *)[tiffStorage elementAt:
		tiffStorageIndex])->isAnim;
	maxAnimFiles = ((ImageStruct *)[tiffStorage elementAt:
		tiffStorageIndex])->maxAnimFiles;
	imageName = ((ImageStruct *)[tiffStorage elementAt:
		tiffStorageIndex])->imageName;
	animDirName = ((ImageStruct *)[tiffStorage elementAt:
		tiffStorageIndex])->animDirName;


	switch(animationIndex){
		case 0:  //create list - read in source Image
			spinAdjustRect.origin.x = 0.0;
			spinAdjustRect.origin.y = 0.0;
			scaledImageRect.origin.x = 0.0;
			scaledImageRect.origin.y = 0.0;
		
			localImageList = [[List alloc] init];
			if(isAnim){ // drop down another level 
				sprintf(buf,"%s/%s.anim/%s.%d.tiff",tiffDirectory, animDirName,
				imageName,animFileIndex);
				animFileIndex++;
				if(animFileIndex > maxAnimFiles)
					animFileIndex = 1;
			}
			else
	    		sprintf(buf,"%s/%s.tiff",tiffDirectory,imageName);

			sourceImage = [[NXBitmapImageRep alloc] initFromFile:buf];
			if(sourceImage == nil){
				printf("couldn't init NXBitmapImageRep from file %s\n",buf);
				animationIndex = 0; 	//get next image if there is one
				tiffStorageIndex++;
				if(tiffStorageIndex > totalTiffCount)
					tiffStorageIndex = 0;
				numberProcessed++;
				break;
			}
			else animationIndex++;
			if(isSpin){
				angle = 0.0;
				angleInc = randBetween(5,10); // objects will spin
											  //at different speeds each time
			}
			break;
		case 1: //create temp buffer size of original image unless spinning
				// if spinning make buffer square

			[sourceImage getSize: &sourceImageSize];


			 if(sourceImageSize.width > sourceImageSize.height){
			 	widthIsSmaller = NO;
				scaledImageRect.size.height = 1; 
				//get percentage that 1 pixel is
				increasePercent = scaledImageRect.size.height /
				 sourceImageSize.height;
				scaledImageRect.size.width = ceil(increasePercent *
				 sourceImageSize.width);
				maxImageSize = (sourceImageSize.width > MAXIMAGESIZE) ?
				 MAXIMAGESIZE: sourceImageSize.width; // smallest number	
			}
			else{
				widthIsSmaller = YES;
				scaledImageRect.size.width = 1; 
				//get percentage that 1 pixel is
				increasePercent = scaledImageRect.size.width /
				 sourceImageSize.width;
				scaledImageRect.size.height = ceil(increasePercent *
				 sourceImageSize.height);
				maxImageSize = (sourceImageSize.height > MAXIMAGESIZE) ?
				 MAXIMAGESIZE: sourceImageSize.height; //smallest number
				
			}
			if(isSpin){ //use diagonal across original image to make a square
						// big enough to handle the image rotating
				maxDiagonal = sqrt((sourceImageSize.width *
				 sourceImageSize.width) +
				 (sourceImageSize.height * sourceImageSize.height));
				spinAdjustRect.size.height = spinAdjustRect.size.width
				 = maxDiagonal;
				 //get center of the new buffer
				spinCenter.x = spinAdjustRect.size.width / 2; 	
				spinCenter.y = spinAdjustRect.size.height / 2;
					 
				buffer = [[NXImage allocFromZone:[self zone]]
			 	 initSize:&spinAdjustRect.size];
			}
			else{
				buffer = [[NXImage allocFromZone:[self zone]]
			 	 initSize:&sourceImageSize];
			} 
	 
		animationIndex++;
			break;
		case 2: // scale and/or rotate - composite the image in buffer
				// this will keep alpha info
				// when you do the rotate, it rotates around origin
				// have to do a translate to bring center of image
				//back to the same place. drawIn method will do the
				//scaling automatically
			if([buffer lockFocus]){
				if(isSpin){
				
					//caculate the real size of the animation images
				
					scaledDiagonal = sqrt((scaledImageRect.size.width *
				 	 scaledImageRect.size.width)
					 + (scaledImageRect.size.height *
					 scaledImageRect.size.height));
					spinScaledImageRect.size.width = scaledDiagonal;
					spinScaledImageRect.size.height = scaledDiagonal;
				
					//find the orgin of where the scaled image is in buffer
				
					spinScaledImageRect.origin.x =(spinCenter.x -
				 	 (scaledDiagonal/2));
					spinScaledImageRect.origin.y = (spinCenter.y -
				 	 (scaledDiagonal/2));
					PSsetgray(0);// black out buffer
					NXRectFill(&spinScaledImageRect);//black out part of buffer
			
					oldCenter.x = scaledImageRect.size.width / 2;
					oldCenter.y = scaledImageRect.size.height / 2;
					
					origAngle = atan(oldCenter.y/oldCenter.x); 
					
					
					distance = sqrt((oldCenter.x * oldCenter.x) +
				 	 (oldCenter.y * oldCenter.y));
					angle += angleInc;		//increment the angle
					 
					// convert degrees to radians

					newCenter.x = cos(origAngle + (angle/57.30)) * distance;
					newCenter.y = sin(origAngle + (angle/57.30)) * distance;
					newTrans.x = (oldCenter.x - newCenter.x);
					newTrans.y = (oldCenter.y - newCenter.y);
					
					//now adjust to center of square buffer
					newTrans.x += (spinCenter.x - oldCenter.x);
					newTrans.y += (spinCenter.y - oldCenter.y);
					
					PStranslate(0.0 + newTrans.x,0.0 + newTrans.y);
					PSrotate(angle);


					if(![sourceImage drawIn:&scaledImageRect])
						printf("resizing image didn't work\n");
					[buffer unlockFocus];
				}
				else {
					if(![sourceImage drawIn:&scaledImageRect])
						printf("resizing image didn't work\n");
					[buffer unlockFocus];
				}
			}
			if(isAnim){
				[sourceImage free];
			}
			animationIndex++;
			break;
		case 3: // create new image correct size
				//build the image object with the actual image centered
				//in a black rectangle 8 pixels bigger on all sides
				// the stars have a max of width and height of 7 
				// and this guarantees that they will get blacked out
				// as the image is moving
			if(isSpin){
				//calculate a square from the scaled image size
				// using the diagonal across the scaled image
	
				adjustedRect.origin.x = 0.0;
				adjustedRect.origin.y = 0.0;
				adjustedRect.size.width = scaledDiagonal+16;
				adjustedRect.size.height = scaledDiagonal+16;
				
				//create the actual image 16 pixels bigger
					
				image = [[NXImage allocFromZone:[self zone]]
			 	 initSize:&adjustedRect.size];
				
				//black it out 
				if([image lockFocus]){
					PSsetgray(0);
					NXRectFill(&adjustedRect);  //black out new image
					[image unlockFocus];
				}
	
			}
			else{
				adjustedRect.origin.x = 0.0;
				adjustedRect.origin.y = 0.0;
				adjustedRect.size.width = scaledImageRect.size.width+16;
				adjustedRect.size.height = scaledImageRect.size.height+16;

				
				//create the image
	
				image = [[NXImage allocFromZone:[self zone]]
			 	 initSize:&adjustedRect.size];
				if([image lockFocus]){
					PSsetgray(0);
					NXRectFill(&adjustedRect);  //black out new image
					[image unlockFocus];
				}
			} 
			animationIndex++;
			break;
		case 4: //composite into the image just created to get rid of alpha
				//center in middle of slightly bigger black image if rotating
				
			offset.x = 8.0;
			offset.y = 8.0;
			if([image lockFocus]){
				if(isSpin){
					[buffer composite:NX_SOVER fromRect:&spinScaledImageRect
				 	 toPoint:&offset];
				}
				else{
					[buffer composite:NX_SOVER fromRect:&scaledImageRect
				 	 toPoint:&offset];
 				}
				 
				[image unlockFocus];
			}
			animationIndex++;
			break;
		case 5:	//now do phony composite, to build
				//offscreen cache - othewise a delay will happen
				//when you composite the first one
			 
#ifdef DEBUG
	//special debug code to see actual images being generated
	// set PSsetgray(1) on previous calls to see the buffers
	PSsetgray(0);
	bogusPoint.x = 250.0;
	bogusPoint.y = 250.0;
	DebugRect.origin.x = 0.0;
	DebugRect.origin.y = 0.0;
	
	[image getSize: &DebugRect.size];
	[image composite:NX_SOVER fromRect:&DebugRect
		toPoint:&bogusPoint];

	bogusPoint.x = 50.0;
	bogusPoint.y = 50.0;
			 
	[buffer getSize: &DebugRect.size];
	[buffer composite:NX_SOVER fromRect:&DebugRect
		toPoint:&bogusPoint];
		
	if(isSpin){			
	PSmoveto(spinScaledImageRect.origin.x+50,
	 spinScaledImageRect.origin.y+50);
	PSlineto(spinScaledImageRect.origin.x+50,
	spinScaledImageRect.origin.y + spinScaledImageRect.size.height+50);
	PSlineto(spinScaledImageRect.origin.x+50 + spinScaledImageRect.size.width,
	spinScaledImageRect.origin.y + spinScaledImageRect.size.height+50);
	PSlineto(spinScaledImageRect.origin.x + spinScaledImageRect.size.width+50,
	spinScaledImageRect.origin.y+50);
	PSlineto(spinScaledImageRect.origin.x+50,spinScaledImageRect.origin.y+50);
	}
	else {
	PSmoveto(scaledImageRect.origin.x+50, scaledImageRect.origin.y+50);
	PSlineto(scaledImageRect.origin.x+50, scaledImageRect.origin.y + 
	scaledImageRect.size.height+50);
	PSlineto(scaledImageRect.origin.x+50 + scaledImageRect.size.width,
	scaledImageRect.origin.y + scaledImageRect.size.height+50);
	PSlineto(scaledImageRect.origin.x + scaledImageRect.size.width+50,
	scaledImageRect.origin.y+50);
	PSlineto(scaledImageRect.origin.x+50, scaledImageRect.origin.y+50);
	}
	PSstroke();
#else
			[image composite:NX_DOVER fromRect:&bogusRect
			 toPoint:&bogusPoint];
#endif


			[localImageList addObject:image];	//add to list
			
			//calculate new size
			if(widthIsSmaller){
				scaledImageRect.size.width += SIZEINC;
				scaledImageRect.size.width =
				 ceil(scaledImageRect.size.width);
				scaledImageRect.size.height =
				 ceil((scaledImageRect.size.width /
				 sourceImageSize.width) * sourceImageSize.height);
			}
			else{
				scaledImageRect.size.height += SIZEINC;
				scaledImageRect.size.height = 
				 ceil(scaledImageRect.size.height);
				scaledImageRect.size.width =
				 ceil((scaledImageRect.size.height /
				 sourceImageSize.height) * sourceImageSize.width);
					 
			}
			
			// if image is now bigger that max allowed size - exit on next pass
			
			if (scaledImageRect.size.width <= maxImageSize &&
			 scaledImageRect.size.height <= maxImageSize){
				animationIndex = 2;
			 	if(isAnim){ // set source image to next one 
					sprintf(buf,"%s/%s.anim/%s.%d.tiff",
					 tiffDirectory, animDirName,imageName,animFileIndex);
					animFileIndex++;
					if(animFileIndex > maxAnimFiles)
						animFileIndex = 1;
						
					//create image for next pass if anim type
					sourceImage = [[NXBitmapImageRep alloc] initFromFile:buf];
					if(sourceImage == nil){//bad anim dir - go on to next image
						printf("couldn't init image file %s\n",buf);
						printf("skipping anim directory\n");
						if([buffer free] != nil)
							printf("buffer not freed\n");;
						numberProcessed++;
						tiffStorageIndex++;
						if(tiffStorageIndex > totalTiffCount)
							tiffStorageIndex = 0;
					}
				}
			}
			else{
				animationIndex++;
			}
			break;
		case 6:	// finish image list
			//printf("completing image # %d\n", tiffStorageIndex);
			numberProcessed++;
			if(!isAnim){
				[sourceImage free];
			}
			if([buffer free] != nil)
				printf("buffer not freed\n");

	    	imageStruct.imageList = localImageList;
			imageStruct.imageName = imageName;
			imageStruct.isSpin = isSpin;
			imageStruct.isMult = isMult;
			imageStruct.isAnim = isAnim;
			imageStruct.animDirName = animDirName;
			imageStruct.maxAnimFiles = maxAnimFiles;
	    	[tiffStorage replaceElementAt:(unsigned int)tiffStorageIndex
			 with:&imageStruct];
			animationIndex = 0;
			animFileIndex = 1;
			tiffStorageIndex++;
			if(tiffStorageIndex >= totalTiffCount)
				tiffStorageIndex = 0;
			break;
	}

	if(numberProcessed >= MAXANIMATIONS || numberProcessed >= totalTiffCount){
		numberProcessed = 0;
		return NO;
	}
	else{
		return YES;  //Tiffs still need to be built
	}
}
- freeTiffs
{
//  all the tiffs brought in
int ii;

List *imageList;
int count;
char *imageName = 0;
char *animDirName = 0;
ImageStruct imageStruct;
int isSpin = 0;
int isAnim = 0;
int isMult = 0;


	for(ii=0; ii < (int)[tiffStorage count];ii++){
		imageList = (List *)((ImageStruct *)[tiffStorage
		elementAt:ii])->imageList;
		if(imageList != nil){
			count = [imageList count];
			if(count){
				[imageList freeObjects];
				[imageList free];
				//kind of ugly - all this to set the storage item
				//imageStruct.imageList to nil - otherwise
				// all other variables in storage record get wiped out
				
				isSpin = ((ImageStruct *)[tiffStorage elementAt:
			 	 ii])->isSpin;
				isMult = ((ImageStruct *)[tiffStorage elementAt:
			 	 ii])->isMult;
				isAnim = ((ImageStruct *)[tiffStorage elementAt:
			 	 ii])->isAnim;
				maxAnimFiles = ((ImageStruct *)[tiffStorage elementAt:
			 	 ii])->maxAnimFiles;
				imageName = ((ImageStruct *)[tiffStorage elementAt:
			 	 ii])->imageName;
				animDirName = ((ImageStruct *)[tiffStorage elementAt:
			 	 ii])->animDirName;
	    		imageStruct.imageList = nil;
				imageStruct.imageName = imageName;
				imageStruct.isSpin = isSpin;
				imageStruct.isMult = isMult;
				imageStruct.isAnim = isAnim;
				imageStruct.animDirName = animDirName;
				imageStruct.maxAnimFiles = maxAnimFiles;
	    		[tiffStorage replaceElementAt:(unsigned int)ii
			 	 with:&imageStruct];
			}
		}	
	}
	return self;
	
}

// creates a tiff record in the tiffStorage for every tiff file encountered.
// the actual tiff lists are built in createTiffs
// looks in Celestial bundle or .Celestial directory in home directory

- buildTiffStorage
{
ImageStruct imageStruct; 

    tiffStorage = [[Storage allocFromZone:[self zone]] initCount:0
    elementSize:sizeof(imageStruct) description:"{iiii@*}"];

    totalTiffCount = [self searchDirectory]; //create list of filenames
           
    return self;
}

- setTiffDirectory: (char *)directory
{
    strcpy(tiffDirectory,directory);
    return self;
}

- (BOOL)isOk:(char *)filename :(int *)isAnim :(int *)isMult :(int *)isSpin
//checks to make sure the filename is .tiff
//sets flags for rotate,anim and mult options

{ 
char *suffix;

	*isSpin = 0;
	*isMult = 0;
	*isAnim = 0;
	
    suffix = rindex(filename,'.');
    if (suffix == 0)
    	return NO;
		
    else if(strlen(suffix) != 5)
        return NO;
		
    else if(strncmp(suffix,".tiff",5) == 0){
    	*suffix  = '\0'; 	/* strip tiff suffix */
		if([self stringMatch:"mult" :filename])
	 		*isMult = 1;
		if([self stringMatch:"spin" :filename])
	 		*isSpin = 1;
		return YES;
		
	}
    else if(strncmp(suffix,".anim",5) == 0){
    	*suffix  = '\0'; 	/* strip anim suffix */
		*isAnim = 1;
		if([self stringMatch:"mult" :filename])
	 		*isMult = 1;
		if([self stringMatch:"spin" :filename])
	 		*isSpin = 1;
		return YES;
	}
	else
		return NO;
}



// look in the directory for all the tiff images
//if .anim directory encountered then get count and filename
// create a list of storage objects that will use them
- (int)searchDirectory
{
ImageStruct imageStruct; 
char animDirectory[MAXPATHLEN + 1];
char filename[MAXPATHLEN + 1];
long basep;
char *buf;
struct direct *dp;
int cc, fd, fileCount = 0;
char dirbuf[8192];
int isAnim,isMult,isSpin,animCount;

	if ((fd = open(tiffDirectory, O_RDONLY, 0644)) > 0) {
		cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
		while (cc) {
	    	dp = (struct direct *)buf;
	    
	    	if ([self isOk:dp->d_name:&isAnim :&isMult :&isSpin]) {
	    		imageStruct.isAnim = isAnim;
	    		imageStruct.isMult = isMult;
			
	    		imageStruct.isSpin = isSpin;
				animCount = 0;
				if(isAnim){
					sprintf(animDirectory,"%s/%s.anim",
				 	tiffDirectory,dp->d_name);
					animCount = (int)[self isAnimOk:animDirectory :filename];
					if(animCount > 0){
						imageStruct.imageName = NXCopyStringBufferFromZone
					 	 (filename,[self zone]);
						imageStruct.animDirName = NXCopyStringBufferFromZone
					 	 (dp->d_name,[self zone]);
						imageStruct.imageList = NULL;
						imageStruct.maxAnimFiles = animCount;
	    				[tiffStorage addElement:&imageStruct];
						fileCount++;
					}
				}
				else {
					imageStruct.imageName = NXCopyStringBufferFromZone(dp->d_name,
	 	 	 	 	 [self zone]);			 
					imageStruct.maxAnimFiles = animCount;
					imageStruct.imageList = NULL;
					imageStruct.animDirName = NULL;
	    			[tiffStorage addElement:&imageStruct];
					fileCount++;

				}
			}
			buf += dp->d_reclen;
			if (buf >= dirbuf + cc) {
				cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
			}
		}
		close(fd);

	}
    return fileCount;
}

- (Storage *)returnTiffStorage
{
	return tiffStorage;
}

- setHomeDirFile:(char *)path
{
char user[15];
char *getlogin();
char *ptr;
struct passwd *getpwuid(), *pwptr = NULL;

    user[0] = 0;

    if (((ptr = getlogin()) == NULL) || !user[0])
        {
            if ((pwptr = getpwuid(getuid())) == NULL)
                sprintf(user, "%d", getuid());
            else
                strcpy(user, pwptr->pw_name);
        }

    if(user){
	strcpy(path,pwptr->pw_dir);
	strcat(path,"/.Celestial");
    }
   return self;	
}
- (BOOL)stringMatch:(char *)string1 : (char *)string2
{
		while(*string2 != '\0'){
			if(strncasecmp(string1,string2,1) == 0){
				if(strncasecmp(string1, string2, strlen(string1)) == 0){
					return YES;
				}
				else{
					string2++;

				}
			}
			else
				string2++;
		}
		return NO;
}

// look in the anim directory for all the tiff images
// it will return the name of the first file that is named filename.1.tiff
		
- (int)isAnimOk: (char *)directory :(char *)filename 
{
// look in the anim directory for all the tiff images
// it will return the name of the file that is named filename.1.tiff
// otherwise return NULL

long basep;
char *buf;
struct direct *dp;
int cc = 0;
int fd = 0;
short found = 0;
int fileCount = 0;
char dirbuf[8192];
char *suffix = NULL;

	
    if ((fd = open(directory, O_RDONLY, 0644)) > 0) {
		cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
		while (cc) {
	    	dp = (struct direct *)buf;
			
			suffix = rindex(dp->d_name,'.');
    		if(strncmp(suffix,".tiff",5) == 0){
	    		if (!found && [self stringMatch:".1.tiff" :dp->d_name]){
					found = 1; //if found don't look anymore
					strncpy(filename,dp->d_name,
					 strlen(dp->d_name)-7);
					filename[(strlen(dp->d_name)-7)] = '\0';
					
				}
				fileCount++;

			}
			
			buf += dp->d_reclen;
	    	if (buf >= dirbuf + cc) {
				cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
			}
		}
	}
	close(fd);
	if(!found) // there was no file "file.1.tiff" in anim dir
		fileCount = 0;
    return fileCount;
}
@end
